﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;
using System.Threading.Tasks;

namespace Colonisator.Classes
{
    /// <summary>
    /// Fournit les éléments de Path Finding pour le jeu.
    /// </summary>
    public class PathFinding
    {
        public static Dictionary<int, ElementPlateau> _pathWay;
        private static List<PointCardinal> liste;
        private static ConcurrentDictionary<ElementPlateau, double> dico;

        private static void InitListe()
        {
            liste = new List<PointCardinal>();
            liste.Add(new Ouest());
            liste.Add(new Est());
            liste.Add(new Nord());
            liste.Add(new Sud());
        }

        public PathFinding(Dictionary<int, ElementPlateau> aPathWay)
        {
            _pathWay = aPathWay;
        }

        public PathFinding()
        {
            _pathWay = new Dictionary<int, ElementPlateau>();
        }

        /// <summary>
        /// Donne le chemin le plus court pour aller de la coordonée source à
        /// la cible. Ne tient compte que des éléments navigables ou non.
        /// Attention, l'élément cible doit être navigable !
        /// </summary>
        /// <returns></returns>
        public static PathFinding DonnerPathBrut(ElementPlateau source, ElementPlateau cible, Plateau plateau)
        {            
            if (source.CoordonneeElement == cible.CoordonneeElement)
                return new PathFinding(new Dictionary<int, ElementPlateau>());
            else
            {
                _pathWay = new Dictionary<int, ElementPlateau>();
                int noSequence = 1;

                // Alimentation du premier élément
                _pathWay.Add(noSequence, source);

                // Calcul de la distance de base
                double distance = DonnerDistanceElements(source.CoordonneeElement.X, cible.CoordonneeElement.X,
                    source.CoordonneeElement.Y, cible.CoordonneeElement.Y);

                // Alimentation du second élément            
                ElementPlateau elementSuivant = DonnerElementSuivant(source, cible, plateau);
                distance = DonnerDistanceElements(elementSuivant.CoordonneeElement.X, cible.CoordonneeElement.X,
                    elementSuivant.CoordonneeElement.Y, cible.CoordonneeElement.Y);

                noSequence++;
                _pathWay.Add(noSequence, elementSuivant);


                // Alimentation de tous les éléments
                while (elementSuivant.CoordonneeElement != cible.CoordonneeElement)
                {
                    noSequence++;
                    elementSuivant = DonnerElementSuivant(elementSuivant, cible, plateau);
                    distance = DonnerDistanceElements(elementSuivant.CoordonneeElement.X, cible.CoordonneeElement.X,
                        elementSuivant.CoordonneeElement.Y, cible.CoordonneeElement.Y);

                    _pathWay.Add(noSequence, elementSuivant);
                }

                // Retour
                return new PathFinding(_pathWay);
            }
        }

        private static double DonnerDistanceElements(int X1, int X2, int Y1, int Y2)
        {
            return Math.Sqrt( Math.Pow(X1 - X2, 2) + Math.Pow(Y1 - Y2, 2));
        }

        private static ElementPlateau DonnerElementSuivant(ElementPlateau elementActu, ElementPlateau cible,
            Plateau unPlateau)
        {
            dico = new ConcurrentDictionary<ElementPlateau, double>();
            InitListe();

            // Traitement des 4 directions
            //Parallel.ForEach(liste, pointCardinal =>
            //    { TraiterDirection(elementActu, pointCardinal, cible, unPlateau); });

            foreach (var item in liste)
            {
                TraiterDirection(elementActu, item, cible, unPlateau);
            }

            // On retourne l'élément le plus proche de la cible
            var localDico = dico.OrderBy(x => x.Value).ToDictionary(x => x.Key, x => x.Value);
            return localDico.SkipWhile(x => _pathWay.ContainsValue(x.Key)).FirstOrDefault().Key;
        }

        private static void TraiterDirection(ElementPlateau elementActu, PointCardinal uneDirection, 
            ElementPlateau cible, Plateau unPlateau)
        {
            var newElement = unPlateau.ElementSuivantSelonDirection(elementActu, uneDirection);

            if (newElement != null && newElement.ElementNavigable)
                dico.GetOrAdd(newElement, DonnerDistanceSelonDirectionElementaire(newElement, cible, unPlateau));
        }

        private static double DonnerDistanceSelonDirectionElementaire(ElementPlateau newElement, ElementPlateau cible, 
            Plateau unPlateau)
        {
            var element = unPlateau.LocaliserElementSurPlateau(newElement);
            return DonnerDistanceElements(newElement.CoordonneeElement.X, cible.CoordonneeElement.X, newElement.CoordonneeElement.Y, cible.CoordonneeElement.Y);
        }

        public override string ToString()
        {
            string result = "";
            if (_pathWay != null)
            {
                foreach (var item in _pathWay)
                {
                    result += item.Key + " : " + item.Value.ToString() + "\n";
                }
            }
            return result;
        }
    }
}
